

# Copyright 2010 István Szentandrási
# <etude-music-player@googlegroups.com>

# This file is part of Etude.
#
# Etude is free software: you can redistribute it and/or modify
# it under the terms of the GNU Lesser General Public License as 
# published by the Free Software Foundation, either version 3 
# of the License, or (at your option) any later version.
#
# Etude is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU Lesser General Public License for more details.
#
# You should have received a copy of the GNU Lesser General Public License
# along with Etude. If not, see <http://www.gnu.org/licenses/>.

"""
Libvisual wrapper for simple needs
"""

cdef extern from "Python.h":
    object PyString_FromStringAndSize(char *, int)
    void PyMem_Free(void *p)
    void* PyMem_Malloc(int n) except NULL


cdef extern from "libvisual/libvisual.h":

    ctypedef enum:
        VISUAL_OK

    ctypedef struct VisAudioSamplePool
    ctypedef enum VisAudioSampleFormatType:
        VISUAL_AUDIO_SAMPLE_FORMAT_NONE = 0,
        VISUAL_AUDIO_SAMPLE_FORMAT_U8,
        VISUAL_AUDIO_SAMPLE_FORMAT_S8,
        VISUAL_AUDIO_SAMPLE_FORMAT_U16,
        VISUAL_AUDIO_SAMPLE_FORMAT_S16,
        VISUAL_AUDIO_SAMPLE_FORMAT_U32,
        VISUAL_AUDIO_SAMPLE_FORMAT_S32,
        VISUAL_AUDIO_SAMPLE_FORMAT_FLOAT,
        VISUAL_AUDIO_SAMPLE_FORMAT_LAST,

    ctypedef enum VisAudioSampleRateType:
        VISUAL_AUDIO_SAMPLE_RATE_NONE = 0,
        VISUAL_AUDIO_SAMPLE_RATE_8000,
        VISUAL_AUDIO_SAMPLE_RATE_11250,
        VISUAL_AUDIO_SAMPLE_RATE_22500,
        VISUAL_AUDIO_SAMPLE_RATE_32000,
        VISUAL_AUDIO_SAMPLE_RATE_44100,
        VISUAL_AUDIO_SAMPLE_RATE_48000,
        VISUAL_AUDIO_SAMPLE_RATE_96000,
        VISUAL_AUDIO_SAMPLE_RATE_LAST,

    ctypedef enum VisAudioSampleChannelType:
        VISUAL_AUDIO_SAMPLE_CHANNEL_NONE,
        VISUAL_AUDIO_SAMPLE_CHANNEL_STEREO

    ctypedef enum VisVideoDepth:
        VISUAL_VIDEO_DEPTH_NONE         = 0,
        VISUAL_VIDEO_DEPTH_8BIT         = 1,
        VISUAL_VIDEO_DEPTH_16BIT        = 2,
        VISUAL_VIDEO_DEPTH_24BIT        = 4,
        VISUAL_VIDEO_DEPTH_32BIT        = 8,
        VISUAL_VIDEO_DEPTH_GL           = 16,
        VISUAL_VIDEO_DEPTH_ENDLIST      = 32,
        VISUAL_VIDEO_DEPTH_ERROR        = -1,
        VISUAL_VIDEO_DEPTH_ALL          = 31
    ctypedef enum VisBinSwitchStyle:
        VISUAL_SWITCH_STYLE_DIRECT,
        VISUAL_SWITCH_STYLE_MORPH

    ctypedef struct VisVideo

    ctypedef int(* VisVideoCustomCompositeFunc)(VisVideo  *dest, VisVideo  *src)

    ctypedef enum VisVideoCompositeType:
        pass

    ctypedef struct VisObject:
        pass
    ctypedef struct VisColor:
        pass
    ctypedef struct VisPalette
    ctypedef struct VisRectangle:
        VisObject       obj,
        int             x,
        int             y,
        int             width,
        int             height
    ctypedef struct VisBuffer:
        pass

    ctypedef struct VisVideo:
        VisObject                        obj,
        VisVideoDepth                    depth,
        int                              width,
        int                              height,
        int                              bpp,
        int                              pitch,
        VisBuffer                       *buffer,
        void                            **pixel_rows,
        VisPalette                      *pal,
        VisVideo                        *parent,
        VisRectangle                     rect,
        VisVideoCompositeType            compositetype,
        VisVideoCustomCompositeFunc      compfunc,
        VisColor                         colorkey,
        unsigned char                          density,

    ctypedef struct VisActor
    ctypedef struct VisBin

    ctypedef struct VisAudio:
        VisObject obj,
        VisAudioSamplePool *samplepool,
        short plugpcm[2][512],
        int energy
    ctypedef struct VisInput

    ctypedef void (* VisBufferDestroyerFunc)(VisBuffer  *buffer)
    ctypedef int(* VisInputUploadCallbackFunc)(VisInput  *input, VisAudio  *audio, void *priv)

    #object
    int visual_object_destroy(VisObject* obj)
    int visual_object_unref(VisObject* obj)

    #actors
    char * visual_actor_get_prev_by_name(char * plugin)
    char * visual_actor_get_next_by_name(char * plugin)
    int visual_actor_valid_by_name(char *plugin)
    int visual_actor_realize(VisActor *actor)
    VisActor *visual_actor_new(char * name)
    int visual_actor_get_supported_depth(VisActor* actor)
    VisPalette *visual_actor_get_palette(VisActor* actor)
    int visual_actor_set_video(VisActor* actor, VisVideo* video)
    int visual_actor_video_negotiate(VisActor* actor, int rundepth, int noevent, int forced)

    #buffer
    int visual_buffer_init(VisBuffer *buf, void *data, int datasize, VisBufferDestroyerFunc func)
    int visual_audio_samplepool_input(VisAudioSamplePool *pool, VisBuffer *buf,
            VisAudioSampleRateType rate,
            VisAudioSampleFormatType format,
            VisAudioSampleChannelType channel)

    #bin
    VisBin *visual_bin_new()
    int visual_bin_sync(VisBin *bin, int noevent)
    int visual_bin_set_supported_depth(VisBin* bin, VisVideoDepth depth)
    int visual_bin_set_video(VisBin* bin, VisVideo* video)
    int visual_bin_set_actor(VisBin* bin, VisActor* actor)
    int visual_bin_set_input(VisBin* bin, VisInput* input)
    int visual_bin_connect_by_names(VisBin* bin, char * actname,
            char * inname)
    VisVideoDepth visual_bin_get_depth(VisBin* bin)
    int visual_bin_depth_changed(VisBin* bin)
    VisInput* visual_bin_get_input(VisBin* bin)
    int visual_bin_realize(VisBin* bin)
    int visual_bin_run(VisBin* bin)
    int visual_bin_switch_actor_by_name(VisBin* bin, char * plugin)
    int visual_bin_set_morph_by_name(VisBin* bin, char * morph)

    #video
    int visual_video_set_dimension(VisVideo *video, int width, int height)
    VisVideo *visual_video_new()
    VisVideo *visual_video_new_with_buffer(int width, int height, VisVideoDepth depth)
    int visual_video_set_depth(VisVideo *video, VisVideoDepth depth)
    int visual_video_set_buffer(VisVideo *video, void* buffer)
    int visual_video_set_pitch(VisVideo *video, int pitch)
    int visual_video_set_palette(VisVideo *video, VisPalette *pal)
    void *visual_video_get_pixels(VisVideo *video)
    int visual_video_allocate_buffer(VisVideo *video)
    int visual_video_bpp_from_depth(VisVideoDepth depth)
    int visual_video_get_size(VisVideo *video)
    int visual_video_get_boundary(VisVideo *video, VisRectangle *rect)
    int visual_video_set_attributes(VisVideo *video, int width, int height, int pitch, VisVideoDepth depth)

    #init
    int visual_init(int *argc, char ***argv)

    #input
    int visual_input_set_callback(VisInput *input, VisInputUploadCallbackFunc func, void *priv)
    int visual_bin_switch_set_style(VisBin *bin, VisBinSwitchStyle style)
    VisInput* visual_input_new(char * input)

    #plugin
    ctypedef struct VisParamContainer
    ctypedef struct VisPluginData

    VisParamContainer* visual_plugin_get_params (VisPluginData  *plugin)
####################################################################

LV_VISUAL_OK = VISUAL_OK
LV_VISUAL_VIDEO_DEPTH_GL = VISUAL_VIDEO_DEPTH_GL

import ctypes

cdef int upload_callback( VisInput* input, VisAudio *audio, void* priv):
    cdef object obj
    obj = <object> priv
    obj._upload_callback(<object>input, <object>audio)
    return 0

cdef class LibVisual:
    """
    Libvisual wrapper
    """
    cdef VisVideo *video
    cdef VisBin *bin
    cdef VisActor *actor
    cdef VisVideoDepth depth
    cdef int bpp
    cdef int width
    cdef int height
    cdef short pcm_data[1024]
    cdef object pluginIsGL
    cdef object available_plugins
    cdef object err_msg

    def __cinit__(self):

        self.video = NULL
        self.bin = NULL
        self.actor = NULL
        self.depth = VISUAL_VIDEO_DEPTH_ERROR
        self.bpp = 0
        self.width = 0
        self.height = 0
        self.pluginIsGL = False
        self.err_msg = ""
        self.available_plugins = []

    def __dealloc__(self):
        if self.video != NULL:

            visual_object_unref(<VisObject *>self.video)
        if self.actor != NULL:
            visual_object_unref(<VisObject *>self.actor)
        if self.bin != NULL:
            visual_object_unref(<VisObject *>self.bin)

    def get_err_msg(self):
        return self.err_msg

    def init(self, width, height, actor_name = "corona"):
        cdef int res
        cdef char * plugin
        self.width = width
        self.height = height

        res = visual_init(NULL, NULL)
        if res < 0:
            self.err_msg = "Couldn't initialize Libvisual."
            return False

        #get available plugins
        plugin = visual_actor_get_next_by_name(plugin)
        while <void *>plugin != NULL:
            self.available_plugins.append(<char *>plugin)
            plugin = visual_actor_get_next_by_name(plugin)

        #create bin
        self.bin = visual_bin_new()
        if <void *>self.bin == NULL:
            self.err_msg = "Unable to create bin."
            return False

        #define input callback
        cdef VisInput* input
        input = visual_input_new(<char *>NULL)
        res = visual_input_set_callback(input, upload_callback, <void*> self)
        if res < 0:
            self.err_msg = "Couldn't connect input callback."
            return False
        visual_bin_set_input(self.bin, input)
        return self.set_actor(width, height, actor_name)

    def set_actor(self, width, height, actor_name):
        cdef int res
        self.width = width
        self.height = height

        #create actor
        if self.actor != NULL:
            visual_object_unref(<VisObject *>self.actor)
        self.actor = visual_actor_new(actor_name)
        if self.actor == NULL:
            self.err_msg = "No actor loaded."
            return False
        res = visual_actor_realize(self.actor)
        if res != VISUAL_OK:
            self.err_msg = "Unable to realize actor."
            return False

        depth = visual_actor_get_supported_depth(self.actor)
        if (depth == VISUAL_VIDEO_DEPTH_NONE
                or depth == VISUAL_VIDEO_DEPTH_ERROR):
            self.err_msg = "Received fubar depthflag."
            return False

        pal_to_24 = False
        self.pluginIsGL = False
        if not (depth & VISUAL_VIDEO_DEPTH_GL):
            if depth & VISUAL_VIDEO_DEPTH_32BIT:
                self.depth = VISUAL_VIDEO_DEPTH_32BIT
            elif depth & VISUAL_VIDEO_DEPTH_24BIT:
                self.depth = VISUAL_VIDEO_DEPTH_24BIT
            elif depth & VISUAL_VIDEO_DEPTH_16BIT:
                self.depth = VISUAL_VIDEO_DEPTH_16BIT
            elif depth & VISUAL_VIDEO_DEPTH_8BIT:
                self.depth = VISUAL_VIDEO_DEPTH_24BIT
                pal_to_24 = True
            else:
                self.err_msg = "Unknown depth"
                return False
        else:
            self.depth = VISUAL_VIDEO_DEPTH_GL
            self.pluginIsGL = True
        self.bpp = visual_video_bpp_from_depth(self.depth)

        #create video
        if self.video != NULL:
            visual_object_unref(<VisObject *> self.video)
            self.video = NULL
        if self.depth != VISUAL_VIDEO_DEPTH_GL:
            self.video = visual_video_new_with_buffer(width, height, self.depth)
            if pal_to_24:
                res = visual_video_set_palette(self.video, visual_actor_get_palette(self.actor))
                if res != 0:
                    self.err_msg = "Unable to set palette on video."
                    return False
        else: #plugin is opengl no buffer needed
            self.video = visual_video_new()
            print 'set depth'
            res = visual_video_set_depth(self.video, self.depth)
            if res != 0:
                self.err_msg = "Unable to set depth on video."
                return False
        res = visual_actor_set_video(self.actor, self.video)
        if res != 0:
            self.err_msg = "Unable to set video for actor."
            return False

        #negotiate
        print 'negotiating'
        res = visual_actor_video_negotiate(self.actor,
                pal_to_24 and VISUAL_VIDEO_DEPTH_24BIT or 0,
                0,
                pal_to_24)
        if res != 0:
            self.err_msg = "Actor/Video negotiation failed."
            return False

        #setting actor
        print 'setting actor'
        res = visual_bin_set_actor(self.bin, self.actor)
        if res != 0:
            self.err_msg = "Unable to connect bin-actor."
            return False

        #realize
        res = visual_bin_realize(self.bin)
        if res != 0:
            self.err_msg = "Unable to realize bin."
            return False
        return True

    def actor_video_negotiate(self, int rundepth, int noevent, int forced):
        cdef int res
        res = visual_actor_video_negotiate(self.actor, rundepth, noevent, forced)
        if res != 0:
            return False
        return True

    def bin_run(self):
        visual_bin_run(self.bin)

    def video_get_pixels(self):
        cdef unsigned char* data
        cdef int size
        size = visual_video_get_size(self.video)
        data = <unsigned char*>visual_video_get_pixels(self.video)
        return PyString_FromStringAndSize(<char *>data, size)

    def video_set_dimension(self, int width, int height):
        cdef int res
        if <void *>self.video == NULL:
            return False
        self.width = width
        self.height = height
        if not self.pluginIsGL:
            res = visual_video_set_attributes(self.video, width, height,
                    self.bpp*width, self.depth)
            if res != 0:
                return False
            #reallocate buffer
            res = visual_video_allocate_buffer(self.video)
            if res != 0:
                print "error allocating buffer"
                return False
        else:
            res = visual_video_set_dimension(self.video, width, height)
            if res != 0:
                print "error setting dimension"
                return False
        return True

    cdef video_allocate_buffer(self):
        cdef int res
        if <void *>self.video == NULL:
            return False
        res = visual_video_allocate_buffer(self.video)
        if res != 0:
            return False
        return True

    def is_GL(self):
        return self.pluginIsGL

    def video_bpp_from_depth(self):
        cdef int res
        if <void *>self.video == NULL:
            return 0
        return visual_video_bpp_from_depth(self.depth)

    def _upload_callback(self, oinput, oaudio):
        cdef VisInput* input
        cdef VisAudio* audio
        cdef VisBuffer buf

        input = <VisInput*> oinput
        audio = <VisAudio*> oaudio

        visual_buffer_init( &buf, <short *>(self.pcm_data), 1024, NULL)
        visual_audio_samplepool_input( audio.samplepool, &buf,
                VISUAL_AUDIO_SAMPLE_RATE_44100,
                VISUAL_AUDIO_SAMPLE_FORMAT_S16,
                VISUAL_AUDIO_SAMPLE_CHANNEL_STEREO
                )

    def set_data_list(self, object ilist):
        n = len(ilist)
        for i from 0 <= i < n:
            self.pcm_data[i] = ilist[i]

    def get_available_plugins(self):
        return self.available_plugins

    def sync(self, int noevent):
        visual_bin_sync(self.bin, noevent)

    def depth_changed(self):
        if visual_bin_depth_changed(self.bin):
            return True
        return False

    def video_get_data(self):
        return (self.video.width, self.video.height, self.video.pitch,
                self.video.bpp)

